#pragma once

#include <cstdint>
#include <iostream>
#include <fstream>

#include <memory>

#include "TxNN/common.h"
#include "utils/threading/spinlock.h"
#include "utils/time/timer.h"

namespace txinfer {

/*
 * Logger
 */
class Logger : public ILogger {
public:
  explicit Logger(Level level) _TX_NOEXCEPT : level_(level) {};

  ~Logger() _TX_NOEXCEPT = default;

  void log(ILogger::Level level, const char* thread_local_buffer) _TX_NOEXCEPT _TX_OVERRIDE {
    if (!isLogLevelEnabled(level) or !thread_local_buffer) {
      return;
    }

    auto current_time = std::chrono::high_resolution_clock::now();

    // multi-threads lock
    lock_.lock();

    PrintTimeToBuffer(time_msg_, current_time);
    _writeMsg(thread_local_buffer);
  }

  bool isLogLevelEnabled(Level level) _TX_NOEXCEPT _TX_OVERRIDE { return (level <= level_); }

private:
  virtual void _writeMsg(const char* msg) _TX_NOEXCEPT = 0;

protected:
  Level level_ = Level::Invalid;

  SpinLock lock_;
  char time_msg_[k_time_len + 1] = {0};
};

/*
 * StdOutLogger
 */
class StdOutLogger : public Logger {
public:
  using Logger::Logger;

  ~StdOutLogger() _TX_NOEXCEPT = default;

private:
  void _writeMsg(const char* msg) _TX_NOEXCEPT _TX_OVERRIDE { printf("%s %s\n", time_msg_, msg); }
};

/*
 * FileFlusher
 */
class FileFlusher {
public:
  explicit FileFlusher(const std::string& file_name) {
    if (file_name.empty()) {
      INFER_STDOUT_FATAL("Empty file name");
      return;
    }

    out_stream_.open(file_name, std::ios::ate | std::ios::app);

    if (!(file_opened_ = out_stream_.is_open())) {
      INFER_STDOUT_WARN("[Warning] Unsucceeded to open log file: %s", file_name.c_str());
      return;
    }

    out_stream_.setf(std::ios::unitbuf);
  }

  virtual ~FileFlusher() { flush(); }

  void write(const char* time_msg, const char* msg) {
    if (file_opened_) {
      out_stream_ << time_msg << " " << msg << std::endl;
    }
  }

  void flush() {
    if (file_opened_) {
      out_stream_.flush();
    }
  }

private:
  std::ofstream out_stream_;
  bool file_opened_ = false;
};

/*
 * FileLogger
 */
class FileLogger : public Logger {
public:
  FileLogger(Level level, std::unique_ptr<FileFlusher>&& flusher) : Logger(level), flusher_(std::move(flusher)) {}
  ~FileLogger() _TX_NOEXCEPT = default;

private:
  void _writeMsg(const char* msg) _TX_NOEXCEPT _TX_OVERRIDE {
    flusher_->write(time_msg_, msg);
    flusher_->flush();
  }

private:
  std::unique_ptr<FileFlusher> flusher_;
};

} // namespace txinfer